package tum0r.webengine.models.utils

import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.exception.ZipException
import net.lingala.zip4j.model.ZipParameters
import net.lingala.zip4j.model.enums.*
import tum0r.webengine.extension.createDir
import tum0r.webengine.extension.isExistsFile
import tum0r.webengine.extension.isExistsFolder
import java.io.File
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets

/**
 * 说明: 处理压缩包封装
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/31 20:14
 *
 * @param[zipPath] zip压缩后的文件保存路径或需要解压的zip文件路径
 * @param[password] 压缩包密码，为空则不加密
 * @param[charset] 压缩包编码
 */
data class Zip @JvmOverloads constructor(var zipPath: String, var password: String = "", var charset: Charset = StandardCharsets.UTF_8) {
    var zipFile: ZipFile? = null

    /**
     * @param[file] 压缩包文件对象
     * @param[password] 压缩包密码，为空则不加密
     * @param[charset] 压缩包编码
     */
    @JvmOverloads
    constructor(file: File, password: String = "", charset: Charset = StandardCharsets.UTF_8) : this(file.absolutePath, password, charset)

    /**
     * 获取压缩文件的文件类型对象
     */
    fun getZipFile(): File = File(zipPath)

    /**
     * 打开Zip文件
     */
    fun openZipFile() {
        zipFile = ZipFile(this.zipPath)
    }

    /**
     * 错误信息
     */
    var errorMessage = ""

    /**
     * zip压缩配置
     */
    private val zipParameters = ZipParameters()

    /**
     * 需要被压缩的文件
     */
    private val files = ArrayList<File>()

    /**
     * 设置压缩方法
     */
    fun setCompressionMethod(method: CompressionMethod) {
        zipParameters.compressionMethod = method
    }

    /**
     * 获取压缩方法
     */
    fun getCompressionMethod(): CompressionMethod = zipParameters.compressionMethod

    /**
     * 设置压缩等级
     */
    fun setCompressionLevel(level: CompressionLevel) {
        zipParameters.compressionLevel = level
    }

    /**
     * 获取压缩等级
     */
    fun getCompressionLevel(): CompressionLevel = zipParameters.compressionLevel

    /**
     * 设置加密方式
     */
    fun setEncryptionMethod(method: EncryptionMethod) {
        zipParameters.encryptionMethod = method
    }

    /**
     * 获取加密方式
     */
    fun getEncryptionMethod(): EncryptionMethod = zipParameters.encryptionMethod

    /**
     * 压缩包是否是加密的
     */
    fun isEncryption(): Boolean = zipParameters.isEncryptFiles

    /**
     * 设置AES加密的密钥长度
     */
    fun setAESLength(length: AesKeyStrength) {
        zipParameters.aesKeyStrength = length
    }

    /**
     * 获取AES加密的密钥长度
     */
    fun getAESLength(): AesKeyStrength = zipParameters.aesKeyStrength

    /**
     * 设置AES加密的版本
     */
    fun setAESVersion(version: AesVersion) {
        zipParameters.aesVersion = version
    }

    /**
     * 获取AES加密的版本
     */
    fun getAESVersion(): AesVersion = zipParameters.aesVersion

    /**
     * 获取要被压缩的文件的数目
     */
    fun filesCount(): Int = this.files.size

    /**
     * 获取要被压缩的文件对象集合
     */
    fun getFiles(): ArrayList<File> = this.files

    /**
     * 添加要被压缩的文件
     */
    fun addFiles(vararg files: File?) {
        for (file in files) {
            if (file == null || !file.exists()) continue
            this.files.add(file)
        }
    }

    /**
     * 添加要被压缩的文件
     */
    fun addFiles(vararg paths: String?) {
        val files = ArrayList<File>()
        for (path in paths) {
            if (path.isNullOrBlank()) continue
            files.add(File(path))
        }
        addFiles(*files.toTypedArray())
    }

    /**
     * 清空要被压缩的文件
     */
    fun clearFile() = this.files.clear()

    /**
     * 删除要被压缩的文件
     */
    fun removeFile(index: Int): File? = if (index >= 0 && index < this.files.size) this.files.removeAt(index) else null

    /**
     * 删除要被压缩的文件
     */
    fun removeFile(fileAbsolutePath: String): File? {
        var index = -1
        for (i in this.files.indices)
            if (this.files[i].absolutePath == fileAbsolutePath) {
                index = i
                break
            }
        return removeFile(index)
    }

    /**
     * 删除要被压缩的文件
     */
    fun removeFile(file: File): Boolean = this.files.remove(file)

    /**
     * 初始化，进行以下默认配置
     * setCompressionMethod(CompressionMethod.DEFLATE)
     * setCompressionLevel(CompressionLevel.NORMAL)
     * setAESLength(AesKeyStrength.KEY_STRENGTH_256)
     * setAESVersion(AesVersion.TWO)
     */
    init {
        setCompressionMethod(CompressionMethod.DEFLATE)
        setCompressionLevel(CompressionLevel.NORMAL)
        setAESLength(AesKeyStrength.KEY_STRENGTH_256)
        setAESVersion(AesVersion.TWO)
    }

    /**
     * 压缩
     *
     * @return 是否压缩成功，false时信息在errorMessage中
     */
    fun compress(): Boolean {
        if (isEncryption()) {
            errorMessage = "密码不能为空"
            return false
        }
        if (zipPath.isBlank()) {
            errorMessage = "压缩包储存路径不能为空"
            return false
        }
        if (this.files.size == 0) {
            errorMessage = "无要被压缩的文件"
            return false
        }
        val file = File(zipPath)
        if (file.isExistsFile(true)) {
            errorMessage = "压缩包文件已存在"
            return false
        }
        file.absoluteFile.parentFile.createDir()
        if (zipFile == null) zipFile = ZipFile(file)
        zipFile!!.charset = charset
        if (isEncryption()) zipFile!!.setPassword(password.toCharArray())
        for (targetFile in files)
            if (targetFile.isExistsFile(true)) zipFile!!.addFile(targetFile, zipParameters)
            else if (targetFile.isExistsFolder(true)) zipFile!!.addFolder(targetFile, zipParameters)
        return true
    }

    /**
     * 解压缩
     *
     * @param[outputDir] 解压缩后文件存放路径，如果不存在会创建
     *
     * @return 是否解压缩成功，false时信息在errorMessage中
     */
    fun decompress(outputDir: File): Boolean {
        val file = File(zipPath)
        if (!file.isExistsFile(true)) {
            errorMessage = "未找到压缩包文件"
            return false
        }
        zipFile = ZipFile(file)
        if (!zipFile!!.isValidZipFile) {
            errorMessage = "压缩文件可能已损坏"
            return false
        }
        if (zipFile!!.isEncrypted) {
            if (password.isBlank()) {
                errorMessage = "请输入密码"
                return false
            }
            zipFile!!.setPassword(password.toCharArray())
        }
        file.createDir()
        try {
            zipFile!!.extractAll(outputDir.absolutePath)
        } catch (e: ZipException) {
            errorMessage = e.message.toString()
            return false
        }
        return true
    }

    /**
     * 解压缩
     *
     * @param[outputPath] 解压缩后文件存放路径，如果不存在会创建
     *
     * @return 是否解压缩成功，false时信息在errorMessage中
     */
    fun decompress(outputPath: String): Boolean = decompress(File(outputPath))

    /**
     * 获取压缩包注释
     */
    fun getComment(): String = zipFile?.comment ?: ""

    /**
     * 设置压缩包注释
     *
     * @param[comment] 注释字符串
     */
    fun setComment(comment: String) {
        zipFile?.comment = comment
    }
}